//
//  ========================================================================
//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.xml;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import org.eclipse.jetty.util.ArrayQueue;
import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.xml.XmlParser.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/* ------------------------------------------------------------ */
/**
 * Configure Objects from XML. This class reads an XML file conforming to the configure.dtd DTD and uses it to configure and object by calling set, put or other
 * methods on the object.
 *
 * <p>
 * The actual XML file format may be changed (eg to spring XML) by implementing the {@link ConfigurationProcessorFactory} interfaces to be found by the
 * <code>ServiceLoader</code> by using the DTD and first tag element in the file. Note that DTD will be null if validation is off.
 *
 */
public class XmlConfiguration
{
    private static final Logger LOG = Log.getLogger(XmlConfiguration.class);

    private static final Class<?>[] __primitives =
    { Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE, Void.TYPE };

    private static final Class<?>[] __primitiveHolders =
    { Boolean.class, Character.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Void.class };

    private static final Class<?>[] __supportedCollections =
    { ArrayList.class,ArrayQueue.class,HashSet.class,Queue.class,List.class,Set.class,Collection.class,};

    private static final Iterable<?> __factoryLoader;

    private static final XmlParser __parser = initParser();

    static
    {
        Iterable<?> loader=null;
        try
        {
            // Use reflection to look up 1.6 service loader
            // loader=ServiceLoader.load(ConfigurationProcessorFactory.class);
            Class<?> slc = ClassLoader.getSystemClassLoader().loadClass("java.util.ServiceLoader");
            Method load = slc.getMethod("load",Class.class);
            loader=(Iterable<?>)load.invoke(null,ConfigurationProcessorFactory.class);
        }
        catch(Exception e)
        {
            LOG.ignore(e);
        }
        finally
        {
            __factoryLoader=loader;
        }
    }

    /* ------------------------------------------------------------ */
    private URL _url;
    private String _dtd;
    private ConfigurationProcessor _processor;
    private final Map<String, Object> _idMap = new HashMap<String, Object>();
    private final Map<String, String> _propertyMap = new HashMap<String, String>();

    /* ------------------------------------------------------------ */
    private synchronized static XmlParser initParser()
    {
        XmlParser parser = new XmlParser();
        URL config60 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_6_0.dtd",true);
        URL config76 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_7_6.dtd",true);
        parser.redirectEntity("configure.dtd",config76);
        parser.redirectEntity("configure_1_0.dtd",config60);
        parser.redirectEntity("configure_1_1.dtd",config60);
        parser.redirectEntity("configure_1_2.dtd",config60);
        parser.redirectEntity("configure_1_3.dtd",config60);
        parser.redirectEntity("configure_6_0.dtd",config60);
        parser.redirectEntity("configure_7_6.dtd",config76);

        parser.redirectEntity("http://jetty.mortbay.org/configure.dtd",config76);
        parser.redirectEntity("http://jetty.eclipse.org/configure.dtd",config76);
        parser.redirectEntity("http://www.eclipse.org/jetty/configure.dtd",config76);

        parser.redirectEntity("-//Mort Bay Consulting//DTD Configure//EN",config76);
        parser.redirectEntity("-//Jetty//Configure//EN",config76);

        return parser;
    }

    /* ------------------------------------------------------------ */
    /**
     * Reads and parses the XML configuration file.
     *
     * @param configuration the URL of the XML configuration
     * @throws IOException if the configuration could not be read
     * @throws SAXException if the configuration could not be parsed
     */
    public XmlConfiguration(URL configuration) throws SAXException, IOException
    {
        synchronized (__parser)
        {
            _url=configuration;
            setConfig(__parser.parse(configuration.toString()));
            _dtd=__parser.getDTD();
        }
    }

    /* ------------------------------------------------------------ */
    /**
     * Reads and parses the XML configuration string.
     *
     * @param configuration String of XML configuration commands excluding the normal XML preamble.
     * The String should start with a "&lt;Configure ....&gt;" element.
     * @throws IOException if the configuration could not be read
     * @throws SAXException if the configuration could not be parsed
     */
    public XmlConfiguration(String configuration) throws SAXException, IOException
    {
        configuration = "<?xml version=\"1.0\"  encoding=\"ISO-8859-1\"?>\n<!DOCTYPE Configure PUBLIC \"-//Mort Bay Consulting//DTD Configure 1.2//EN\" \"http://jetty.eclipse.org/configure_1_2.dtd\">"
                + configuration;
        InputSource source = new InputSource(new StringReader(configuration));
        synchronized (__parser)
        {
            setConfig( __parser.parse(source));
            _dtd=__parser.getDTD();
        }
    }

    /* ------------------------------------------------------------ */
    /**
     * Reads and parses the XML configuration stream.
     *
     * @param configuration An input stream containing a complete configuration file
     * @throws IOException if the configuration could not be read
     * @throws SAXException if the configuration could not be parsed
     */
    public XmlConfiguration(InputStream configuration) throws SAXException, IOException
    {
        InputSource source = new InputSource(configuration);
        synchronized (__parser)
        {
            setConfig(__parser.parse(source));
            _dtd=__parser.getDTD();
        }
    }

    /* ------------------------------------------------------------ */
    private void setConfig(XmlParser.Node config)
    {
        if ("Configure".equals(config.getTag()))
        {
            _processor=new JettyXmlConfiguration();
        }
        else if (__factoryLoader!=null)
        {
            for ( Object factory : __factoryLoader)
            {
                // use reflection to get 1.6 methods
                Method gcp;
                try
                {
                    gcp = factory.getClass().getMethod("getConfigurationProcessor",String.class,String.class);
                    _processor = (ConfigurationProcessor) gcp.invoke(factory,_dtd,config.getTag());
                }
                catch (Exception e)
                {
                    LOG.warn(e);
                }
                if (_processor!=null)
                    break;
            }

            if (_processor==null)
                throw new IllegalStateException("Unknown configuration type: "+config.getTag()+" in "+this);
        }
        else
        {
            throw new IllegalArgumentException("Unknown XML tag:"+config.getTag());
        }
        _processor.init(_url,config,_idMap, _propertyMap);
    }


    /* ------------------------------------------------------------ */
    public Map<String, Object> getIdMap()
    {
        return _idMap;
    }

    /* ------------------------------------------------------------ */
    /**
     * @param map the ID map
     * @deprecated use {@link #getIdMap()}.put(...)
     */
    @Deprecated
    public void setIdMap(Map<String, Object> map)
    {
        _idMap.clear();
        _idMap.putAll(map);
    }

    /* ------------------------------------------------------------ */
    /**
     * @param map the properties map
     * @deprecated use {@link #getProperties()}.putAll(...)
     */
    @Deprecated
    public void setProperties(Map<String, String> map)
    {
        _propertyMap.clear();
        _propertyMap.putAll(map);
    }

    /* ------------------------------------------------------------ */
    public Map<String, String> getProperties()
    {
        return _propertyMap;
    }

    /* ------------------------------------------------------------ */
    /**
     * Applies the XML configuration script to the given object.
     *
     * @param obj The object to be configured, which must be of a type or super type
     * of the class attribute of the &lt;Configure&gt; element.
     * @throws Exception if the configuration fails
     * @return the configured object
     */
    public Object configure(Object obj) throws Exception
    {
        return _processor.configure(obj);
    }

    /* ------------------------------------------------------------ */
    /**
     * Applies the XML configuration script.
     * If the root element of the configuration has an ID, an object is looked up by ID and its type checked
     * against the root element's type.
     * Otherwise a new object of the type specified by the root element is created.
     *
     * @return The newly created configured object.
     * @throws Exception if the configuration fails
     */
    public Object configure() throws Exception
    {
        return _processor.configure();
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    private static class JettyXmlConfiguration implements ConfigurationProcessor
    {
        XmlParser.Node _config;
        Map<String, Object> _idMap;
        Map<String, String> _propertyMap;

        public void init(URL url, XmlParser.Node config, Map<String, Object> idMap, Map<String, String> properties)
        {
            _config=config;
            _idMap=idMap;
            _propertyMap=properties;
        }

        /* ------------------------------------------------------------ */
        public Object configure(Object obj) throws Exception
        {
            // Check the class of the object
            Class<?> oClass = nodeClass(_config);
            if (oClass != null && !oClass.isInstance(obj))
            {
                String loaders = (oClass.getClassLoader()==obj.getClass().getClassLoader())?"":"Object Class and type Class are from different loaders.";
                throw new IllegalArgumentException("Object of class '"+obj.getClass().getCanonicalName()+"' is not of type '" + oClass.getCanonicalName()+"'. "+loaders);
            }
            configure(obj,_config,0);
            return obj;
        }

        /* ------------------------------------------------------------ */
        public Object configure() throws Exception
        {
            Class<?> oClass = nodeClass(_config);

            String id = _config.getAttribute("id");
            Object obj = id == null?null:_idMap.get(id);

            if (obj == null && oClass != null)
                obj = oClass.newInstance();

            if (oClass != null && !oClass.isInstance(obj))
                throw new ClassCastException(oClass.toString());

            configure(obj,_config,0);
            return obj;
        }

        /* ------------------------------------------------------------ */
        private static Class<?> nodeClass(XmlParser.Node node) throws ClassNotFoundException
        {
            String className = node.getAttribute("class");
            if (className == null)
                return null;

            return Loader.loadClass(XmlConfiguration.class,className,true);
        }

        /* ------------------------------------------------------------ */
        /**
         * Recursive configuration routine.
         * This method applies the nested Set, Put, Call, etc. elements to the given object.
         *
         * @param obj the object to configure
         * @param cfg the XML nodes of the configuration
         * @param i the index of the XML nodes
         * @throws Exception if the configuration fails
         */
        public void configure(Object obj, XmlParser.Node cfg, int i) throws Exception
        {
            String id = cfg.getAttribute("id");
            if (id != null)
                _idMap.put(id,obj);

            for (; i < cfg.size(); i++)
            {
                Object o = cfg.get(i);
                if (o instanceof String)
                    continue;
                XmlParser.Node node = (XmlParser.Node)o;

                try
                {
                    String tag = node.getTag();
                    if ("Set".equals(tag))
                        set(obj,node);
                    else if ("Put".equals(tag))
                        put(obj,node);
                    else if ("Call".equals(tag))
                        call(obj,node);
                    else if ("Get".equals(tag))
                        get(obj,node);
                    else if ("New".equals(tag))
                        newObj(obj,node);
                    else if ("Array".equals(tag))
                        newArray(obj,node);
                    else if ("Ref".equals(tag))
                        refObj(obj,node);
                    else if ("Property".equals(tag))
                        propertyObj(node);
                    else
                        throw new IllegalStateException("Unknown tag: " + tag);
                }
                catch (Exception e)
                {
                    LOG.warn("Config error at " + node,e.toString());
                    throw e;
                }
            }
        }

        /* ------------------------------------------------------------ */
        /*
         * Call a set method. This method makes a best effort to find a matching set method. The type of the value is used to find a suitable set method by 1.
         * Trying for a trivial type match. 2. Looking for a native type match. 3. Trying all correctly named methods for an auto conversion. 4. Attempting to
         * construct a suitable value from original value. @param obj
         *
         * @param node
         */
        private void set(Object obj, XmlParser.Node node) throws Exception
        {
            String attr = node.getAttribute("name");
            String name = "set" + attr.substring(0,1).toUpperCase(Locale.ENGLISH) + attr.substring(1);
            Object value = value(obj,node);
            Object[] arg =
            { value };

            Class<?> oClass = nodeClass(node);
            if (oClass != null)
                obj = null;
            else
                oClass = obj.getClass();

            Class<?>[] vClass =
            { Object.class };
            if (value != null)
                vClass[0] = value.getClass();

            if (LOG.isDebugEnabled())
                LOG.debug("XML " + (obj != null?obj.toString():oClass.getName()) + "." + name + "(" + value + ")");

            // Try for trivial match
            try
            {
                Method set = oClass.getMethod(name,vClass);
                set.invoke(obj,arg);
                return;
            }
            catch (IllegalArgumentException e)
            {
                LOG.ignore(e);
            }
            catch (IllegalAccessException e)
            {
                LOG.ignore(e);
            }
            catch (NoSuchMethodException e)
            {
                LOG.ignore(e);
            }

            // Try for native match
            try
            {
                Field type = vClass[0].getField("TYPE");
                vClass[0] = (Class<?>)type.get(null);
                Method set = oClass.getMethod(name,vClass);
                set.invoke(obj,arg);
                return;
            }
            catch (NoSuchFieldException e)
            {
                LOG.ignore(e);
            }
            catch (IllegalArgumentException e)
            {
                LOG.ignore(e);
            }
            catch (IllegalAccessException e)
            {
                LOG.ignore(e);
            }
            catch (NoSuchMethodException e)
            {
                LOG.ignore(e);
            }

            // Try a field
            try
            {
                Field field = oClass.getField(attr);
                if (Modifier.isPublic(field.getModifiers()))
                {
                    field.set(obj,value);
                    return;
                }
            }
            catch (NoSuchFieldException e)
            {
                LOG.ignore(e);
            }

            // Search for a match by trying all the set methods
            Method[] sets = oClass.getMethods();
            Method set = null;
            for (int s = 0; sets != null && s < sets.length; s++)
            {
                Class<?>[] paramTypes = sets[s].getParameterTypes();
                if (name.equals(sets[s].getName()) && paramTypes.length == 1)
                {

                    // lets try it
                    try
                    {
                        set = sets[s];
                        sets[s].invoke(obj,arg);
                        return;
                    }
                    catch (IllegalArgumentException e)
                    {
                        LOG.ignore(e);
                    }
                    catch (IllegalAccessException e)
                    {
                        LOG.ignore(e);
                    }

                    try
                    {
                        for (Class<?> c : __supportedCollections)
                            if (paramTypes[0].isAssignableFrom(c))
                            {
                                sets[s].invoke(obj,convertArrayToCollection(value,c));
                                return;
                            }
                    }
                    catch (IllegalAccessException e)
                    {
                        LOG.ignore(e);
                    }
                }
            }

            // Try converting the arg to the last set found.
            if (set != null)
            {
                try
                {
                    Class<?> sClass = set.getParameterTypes()[0];
                    if (sClass.isPrimitive())
                    {
                        for (int t = 0; t < __primitives.length; t++)
                        {
                            if (sClass.equals(__primitives[t]))
                            {
                                sClass = __primitiveHolders[t];
                                break;
                            }
                        }
                    }
                    Constructor<?> cons = sClass.getConstructor(vClass);
                    arg[0] = cons.newInstance(arg);
                    set.invoke(obj,arg);
                    return;
                }
                catch (NoSuchMethodException e)
                {
                    LOG.ignore(e);
                }
                catch (IllegalAccessException e)
                {
                    LOG.ignore(e);
                }
                catch (InstantiationException e)
                {
                    LOG.ignore(e);
                }
            }

            // No Joy
            throw new NoSuchMethodException(oClass + "." + name + "(" + vClass[0] + ")");
        }

        /**
         * @param array the array to convert
         * @param collectionType the desired collection type
         * @return a collection of the desired type if the array can be converted
         */
        private static Collection<?> convertArrayToCollection(Object array, Class<?> collectionType)
        {
            Collection<?> collection = null;
            if (array.getClass().isArray())
            {
                if (collectionType.isAssignableFrom(ArrayList.class))
                    collection = convertArrayToArrayList(array);
                else if (collectionType.isAssignableFrom(HashSet.class))
                    collection = new HashSet<Object>(convertArrayToArrayList(array));
                else if (collectionType.isAssignableFrom(ArrayQueue.class))
                {
                    ArrayQueue<Object> q= new ArrayQueue<Object>();
                    q.addAll(convertArrayToArrayList(array));
                    collection=q;
                }
            }
            if (collection==null)
                throw new IllegalArgumentException("Can't convert \"" + array.getClass() + "\" to " + collectionType);
            return collection;
        }

        /* ------------------------------------------------------------ */
        private static ArrayList<Object> convertArrayToArrayList(Object array)
        {
            int length = Array.getLength(array);
            ArrayList<Object> list = new ArrayList<Object>(length);
            for (int i = 0; i < length; i++)
                list.add(Array.get(array,i));
            return list;
        }

        /* ------------------------------------------------------------ */
        /*
         * Call a put method.
         *
         * @param obj @param node
         */
        private void put(Object obj, XmlParser.Node node) throws Exception
        {
            if (!(obj instanceof Map))
                throw new IllegalArgumentException("Object for put is not a Map: " + obj);
            @SuppressWarnings("unchecked")
            Map<Object, Object> map = (Map<Object, Object>)obj;

            String name = node.getAttribute("name");
            Object value = value(obj,node);
            map.put(name,value);
            if (LOG.isDebugEnabled())
                LOG.debug("XML " + obj + ".put(" + name + "," + value + ")");
        }

        /* ------------------------------------------------------------ */
        /*
         * Call a get method. Any object returned from the call is passed to the configure method to consume the remaining elements. @param obj @param node
         *
         * @return @exception Exception
         */
        private Object get(Object obj, XmlParser.Node node) throws Exception
        {
            Class<?> oClass = nodeClass(node);
            if (oClass != null)
                obj = null;
            else
                oClass = obj.getClass();

            String name = node.getAttribute("name");
            String id = node.getAttribute("id");
            if (LOG.isDebugEnabled())
                LOG.debug("XML get " + name);

            try
            {
                // try calling a getXxx method.
                Method method = oClass.getMethod("get" + name.substring(0,1).toUpperCase(Locale.ENGLISH) + name.substring(1),(java.lang.Class[])null);
                obj = method.invoke(obj,(java.lang.Object[])null);
                configure(obj,node,0);
            }
            catch (NoSuchMethodException nsme)
            {
                try
                {
                    Field field = oClass.getField(name);
                    obj = field.get(obj);
                    configure(obj,node,0);
                }
                catch (NoSuchFieldException nsfe)
                {
                    throw nsme;
                }
            }
            if (id != null)
                _idMap.put(id,obj);
            return obj;
        }

        /* ------------------------------------------------------------ */
        /*
         * Call a method. A method is selected by trying all methods with matching names and number of arguments. Any object returned from the call is passed to
         * the configure method to consume the remaining elements. Note that if this is a static call we consider only methods declared directly in the given
         * class. i.e. we ignore any static methods in superclasses. @param obj
         *
         * @param node @return @exception Exception
         */
        private Object call(Object obj, XmlParser.Node node) throws Exception
        {
            String id = node.getAttribute("id");
            Class<?> oClass = nodeClass(node);
            if (oClass != null)
                obj = null;
            else if (obj != null)
                oClass = obj.getClass();
            if (oClass == null)
                throw new IllegalArgumentException(node.toString());

            int size = 0;
            int argi = node.size();
            for (int i = 0; i < node.size(); i++)
            {
                Object o = node.get(i);
                if (o instanceof String)
                    continue;
                if (!((XmlParser.Node)o).getTag().equals("Arg"))
                {
                    argi = i;
                    break;
                }
                size++;
            }

            Object[] arg = new Object[size];
            for (int i = 0, j = 0; j < size; i++)
            {
                Object o = node.get(i);
                if (o instanceof String)
                    continue;
                arg[j++] = value(obj,(XmlParser.Node)o);
            }

            String method = node.getAttribute("name");
            if (LOG.isDebugEnabled())
                LOG.debug("XML call " + method);

            try
            {
                Object n= TypeUtil.call(oClass,method,obj,arg);
                if (id != null)
                    _idMap.put(id,n);
                configure(n,node,argi);
                return n;
            }
            catch (NoSuchMethodException e)
            {
                IllegalStateException ise = new IllegalStateException("No Method: " + node + " on " + oClass);
                ise.initCause(e);
                throw ise;
            }

        }

        /* ------------------------------------------------------------ */
        /*
         * Create a new value object.
         *
         * @param obj @param node @return @exception Exception
         */
        private Object newObj(Object obj, XmlParser.Node node) throws Exception
        {
            Class<?> oClass = nodeClass(node);
            String id = node.getAttribute("id");
            int size = 0;
            int argi = node.size();
            for (int i = 0; i < node.size(); i++)
            {
                Object o = node.get(i);
                if (o instanceof String)
                    continue;
                if (!((XmlParser.Node)o).getTag().equals("Arg"))
                {
                    argi = i;
                    break;
                }
                size++;
            }

            Object[] arg = new Object[size];
            for (int i = 0, j = 0; j < size; i++)
            {
                Object o = node.get(i);
                if (o instanceof String)
                    continue;
                arg[j++] = value(obj,(XmlParser.Node)o);
            }

            if (LOG.isDebugEnabled())
                LOG.debug("XML new " + oClass);

            // Lets just try all constructors for now
            Constructor<?>[] constructors = oClass.getConstructors();
            for (int c = 0; constructors != null && c < constructors.length; c++)
            {
                if (constructors[c].getParameterTypes().length != size)
                    continue;

                Object n = null;
                boolean called = false;
                try
                {
                    n = constructors[c].newInstance(arg);
                    called = true;
                }
                catch (IllegalAccessException e)
                {
                    LOG.ignore(e);
                }
                catch (InstantiationException e)
                {
                    LOG.ignore(e);
                }
                catch (IllegalArgumentException e)
                {
                    LOG.ignore(e);
                }
                if (called)
                {
                    if (id != null)
                        _idMap.put(id,n);
                    configure(n,node,argi);
                    return n;
                }
            }

            throw new IllegalStateException("No Constructor: " + node + " on " + obj);
        }

        /* ------------------------------------------------------------ */
        /*
         * Reference an id value object.
         *
         * @param obj @param node @return @exception NoSuchMethodException @exception ClassNotFoundException @exception InvocationTargetException
         */
        private Object refObj(Object obj, XmlParser.Node node) throws Exception
        {
            String id = node.getAttribute("id");
            obj = _idMap.get(id);
            if (obj == null)
                throw new IllegalStateException("No object for id=" + id);
            configure(obj,node,0);
            return obj;
        }

        /* ------------------------------------------------------------ */
        /*
         * Create a new array object.
         */
        private Object newArray(Object obj, XmlParser.Node node) throws Exception
        {

            // Get the type
            Class<?> aClass = java.lang.Object.class;
            String type = node.getAttribute("type");
            final String id = node.getAttribute("id");
            if (type != null)
            {
                aClass = TypeUtil.fromName(type);
                if (aClass == null)
                {
                    if ("String".equals(type))
                        aClass = java.lang.String.class;
                    else if ("URL".equals(type))
                        aClass = java.net.URL.class;
                    else if ("InetAddress".equals(type))
                        aClass = java.net.InetAddress.class;
                    else
                        aClass = Loader.loadClass(XmlConfiguration.class,type,true);
                }
            }

            Object al = null;

            for (Object nodeObject : node)
            {
                XmlParser.Node item = (Node)nodeObject;
                String nid = item.getAttribute("id");
                Object v = value(obj,item);
                al = LazyList.add(al,(v == null && aClass.isPrimitive())?0:v);
                if (nid != null)
                    _idMap.put(nid,v);
            }

            Object array = LazyList.toArray(al,aClass);
            if (id != null)
                _idMap.put(id,array);
            return array;
        }

        /* ------------------------------------------------------------ */
        /*
         * Create a new map object.
         */
        private Object newMap(Object obj, XmlParser.Node node) throws Exception
        {
            String id = node.getAttribute("id");

            Map<Object, Object> map = new HashMap<Object, Object>();
            if (id != null)
                _idMap.put(id,map);

            for (Object o : node)
            {
                if (o instanceof String)
                    continue;
                XmlParser.Node entry = (XmlParser.Node)o;
                if (!entry.getTag().equals("Entry"))
                    throw new IllegalStateException("Not an Entry");

                XmlParser.Node key = null;
                XmlParser.Node value = null;

                for (Object object : entry)
                {
                    if (object instanceof String)
                        continue;
                    XmlParser.Node item = (XmlParser.Node)object;
                    if (!item.getTag().equals("Item"))
                        throw new IllegalStateException("Not an Item");
                    if (key == null)
                        key = item;
                    else
                        value = item;
                }

                if (key == null || value == null)
                    throw new IllegalStateException("Missing Item in Entry");
                String kid = key.getAttribute("id");
                String vid = value.getAttribute("id");

                Object k = value(obj,key);
                Object v = value(obj,value);
                map.put(k,v);

                if (kid != null)
                    _idMap.put(kid,k);
                if (vid != null)
                    _idMap.put(vid,v);
            }

            return map;
        }

        /* ------------------------------------------------------------ */
        /*
         * Get a Property.
         *
         * @param node
         * @return
         * @exception Exception
         */
        private Object propertyObj(XmlParser.Node node) throws Exception
        {
            String id = node.getAttribute("id");
            String name = node.getAttribute("name");
            String defaultValue = node.getAttribute("default");
            Object prop;
            if (_propertyMap != null && _propertyMap.containsKey(name))
                prop = _propertyMap.get(name);
            else
                prop = defaultValue;
            if (id != null)
                _idMap.put(id,prop);
            if (prop != null)
                configure(prop,node,0);
            return prop;
        }


        /* ------------------------------------------------------------ */
        /*
         * Get the value of an element. If no value type is specified, then white space is trimmed out of the value. If it contains multiple value elements they
         * are added as strings before being converted to any specified type. @param node
         */
        private Object value(Object obj, XmlParser.Node node) throws Exception
        {
            Object value;

            // Get the type
            String type = node.getAttribute("type");

            // Try a ref lookup
            String ref = node.getAttribute("ref");
            if (ref != null)
            {
                value = _idMap.get(ref);
            }
            else
            {
                // handle trivial case
                if (node.size() == 0)
                {
                    if ("String".equals(type))
                        return "";
                    return null;
                }

                // Trim values
                int first = 0;
                int last = node.size() - 1;

                // Handle default trim type
                if (type == null || !"String".equals(type))
                {
                    // Skip leading white
                    Object item;
                    while (first <= last)
                    {
                        item = node.get(first);
                        if (!(item instanceof String))
                            break;
                        item = ((String)item).trim();
                        if (((String)item).length() > 0)
                            break;
                        first++;
                    }

                    // Skip trailing white
                    while (first < last)
                    {
                        item = node.get(last);
                        if (!(item instanceof String))
                            break;
                        item = ((String)item).trim();
                        if (((String)item).length() > 0)
                            break;
                        last--;
                    }

                    // All white, so return null
                    if (first > last)
                        return null;
                }

                if (first == last)
                    // Single Item value
                    value = itemValue(obj,node.get(first));
                else
                {
                    // Get the multiple items as a single string
                    StringBuilder buf = new StringBuilder();
                    for (int i = first; i <= last; i++)
                    {
                        Object item = node.get(i);
                        buf.append(itemValue(obj,item));
                    }
                    value = buf.toString();
                }
            }

            // Untyped or unknown
            if (value == null)
            {
                if ("String".equals(type))
                    return "";
                return null;
            }

            // Try to type the object
            if (type == null)
            {
                if (value instanceof String)
                    return ((String)value).trim();
                return value;
            }

            if (isTypeMatchingClass(type,String.class))
                return value.toString();

            Class<?> pClass = TypeUtil.fromName(type);
            if (pClass != null)
                return TypeUtil.valueOf(pClass,value.toString());

            if (isTypeMatchingClass(type,URL.class))
            {
                if (value instanceof URL)
                    return value;
                try
                {
                    return new URL(value.toString());
                }
                catch (MalformedURLException e)
                {
                    throw new InvocationTargetException(e);
                }
            }

            if (isTypeMatchingClass(type,InetAddress.class))
            {
                if (value instanceof InetAddress)
                    return value;
                try
                {
                    return InetAddress.getByName(value.toString());
                }
                catch (UnknownHostException e)
                {
                    throw new InvocationTargetException(e);
                }
            }

            for (Class<?> collectionClass : __supportedCollections)
            {
                if (isTypeMatchingClass(type,collectionClass))
                    return convertArrayToCollection(value,collectionClass);
            }

            throw new IllegalStateException("Unknown type " + type);
        }

        /* ------------------------------------------------------------ */
        private static boolean isTypeMatchingClass(String type, Class<?> classToMatch)
        {
            return classToMatch.getSimpleName().equalsIgnoreCase(type) || classToMatch.getName().equals(type);
        }

        /* ------------------------------------------------------------ */
        /*
         * Get the value of a single element. @param obj @param item @return @exception Exception
         */
        private Object itemValue(Object obj, Object item) throws Exception
        {
            // String value
            if (item instanceof String)
                return item;

            XmlParser.Node node = (XmlParser.Node)item;
            String tag = node.getTag();
            if ("Call".equals(tag))
                return call(obj,node);
            if ("Get".equals(tag))
                return get(obj,node);
            if ("New".equals(tag))
                return newObj(obj,node);
            if ("Ref".equals(tag))
                return refObj(obj,node);
            if ("Array".equals(tag))
                return newArray(obj,node);
            if ("Map".equals(tag))
                return newMap(obj,node);
            if ("Property".equals(tag))
                return propertyObj(node);

            if ("SystemProperty".equals(tag))
            {
                String name = node.getAttribute("name");
                String defaultValue = node.getAttribute("default");
                return System.getProperty(name,defaultValue);
            }

            if ("Env".equals(tag))
            {
                String name = node.getAttribute("name");
                String defaultValue = node.getAttribute("default");
                String value=System.getenv(name);
                return value==null?defaultValue:value;
            }

            LOG.warn("Unknown value tag: " + node,new Throwable());
            return null;
        }
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /**
     * Run the XML configurations as a main application. The command line is used to obtain properties files (must be named '*.properties') and XmlConfiguration
     * files.
     * <p>
     * Any property file on the command line is added to a combined Property instance that is passed to each configuration file via
     * {@link XmlConfiguration#setProperties(Map)}.
     * <p>
     * Each configuration file on the command line is used to create a new XmlConfiguration instance and the {@link XmlConfiguration#configure()} method is used
     * to create the configured object. If the resulting object is an instance of {@link LifeCycle}, then it is started.
     * <p>
     * Any IDs created in a configuration are passed to the next configuration file on the command line using {@link #getIdMap()} and {@link #setIdMap(Map)} .
     * This allows objects with IDs created in one config file to be referenced in subsequent config files on the command line.
     *
     * @param args
     *            array of property and xml configuration filenames or {@link Resource}s.
     * @throws Exception if the XML configurations cannot be run
     */
    public static void main(final String[] args) throws Exception
    {

        final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();

        AccessController.doPrivileged(new PrivilegedAction<Object>()
        {
            public Object run()
            {
                try
                {

                    Properties properties = null;

                    // Look for properties from start.jar
                    try
                    {
                        Class<?> config = XmlConfiguration.class.getClassLoader().loadClass("org.eclipse.jetty.start.Config");
                        properties = (Properties)config.getMethod("getProperties").invoke(null);
                        LOG.debug("org.eclipse.jetty.start.Config properties = {}",properties);
                    }
                    catch (NoClassDefFoundError e)
                    {
                        LOG.ignore(e);
                    }
                    catch (ClassNotFoundException e)
                    {
                        LOG.ignore(e);
                    }
                    catch (Exception e)
                    {
                        LOG.warn(e);
                    }

                    // If no start.config properties, use clean slate
                    if (properties == null)
                    {
                        properties = new Properties();
                        // Add System Properties
                        Enumeration<?> ensysprop = System.getProperties().propertyNames();
                        while (ensysprop.hasMoreElements())
                        {
                            String name = (String)ensysprop.nextElement();
                            properties.put(name,System.getProperty(name));
                        }
                    }

                    // For all arguments, load properties or parse XMLs
                    XmlConfiguration last = null;
                    Object[] obj = new Object[args.length];
                    for (int i = 0; i < args.length; i++)
                    {
                        if (args[i].toLowerCase(Locale.ENGLISH).endsWith(".properties"))
                        {
                            properties.load(Resource.newResource(args[i]).getInputStream());
                        }
                        else
                        {
                            XmlConfiguration configuration = new XmlConfiguration(Resource.newResource(args[i]).getURL());
                            if (last != null)
                                configuration.getIdMap().putAll(last.getIdMap());
                            if (properties.size() > 0)
                            {
                                Map<String, String> props = new HashMap<String, String>();
                                for (Object key : properties.keySet())
                                {
                                    props.put(key.toString(),String.valueOf(properties.get(key)));
                                }
                                configuration.getProperties().putAll(props);
                            }
                            obj[i] = configuration.configure();
                            last = configuration;
                        }
                    }

                    // For all objects created by XmlConfigurations, start them if they are lifecycles.
                    for (int i = 0; i < args.length; i++)
                    {
                        if (obj[i] instanceof LifeCycle)
                        {
                            LifeCycle lc = (LifeCycle)obj[i];
                            if (!lc.isRunning())
                                lc.start();
                        }
                    }
                }
                catch (Exception e)
                {
                    LOG.debug(Log.EXCEPTION,e);
                    exception.set(e);
                }
                return null;
            }
        });

        Throwable th = exception.get();
        if (th != null)
        {
            if (th instanceof RuntimeException)
                throw (RuntimeException)th;
            else if (th instanceof Exception)
                throw (Exception)th;
            else if (th instanceof Error)
                throw (Error)th;
            throw new Error(th);
        }
    }
}
